/*******************************************************************************
 ** Name: main.cpp                                                            **
 ** Description:                                                              **
 **                                                                           **
 ** Open Source Initiative (OSI) Approved 2-clause BSD License                **
 **                                                                           **
 **  Copyright (c) 2009-2010 Barry "Gian" James  <bjames@munixos.net>         **
 **  All rights reserved.                                                     **
 **                                                                           **
 **  Redistribution and use in source and binary forms, with or without       **
 **  modification, are permitted provided that the following conditions are   **
 **  met:                                                                     **
 **                                                                           **
 **  * Redistributions of source code must retain the above copyright notice, **
 **    this list of conditions and the following disclaimer.                  **
 **  * Redistributions in binary form must reproduce the above copyright      **
 **    notice, this list of conditions and the following disclaimer in the    **
 **    documentation and/or other materials provided with the distribution.   **
 **                                                                           **
 **  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS      **
 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED **
 ** TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A           **
 ** PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT        **
 ** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,    **
 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED  **
 ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR    **
 ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF    **
 ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING      **
 ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS        **
 ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.              **
 **                                                                           **
 ******************************************************************************/
// Modified $Date: 2010-03-12 16:27:25 -0700 (Fri, 12 Mar 2010) $ by $Author: bjames $
#define _main_cpp_ID    "$Id: stat.cpp 128 2010-03-12 23:27:25Z bjames $"

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/ordered_index.hpp>
using boost::multi_index_container;
using namespace boost::multi_index;
#include <boost/asio.hpp>
#include <odb/database.hxx>
#include <odb/transaction.hxx>
#include <odb/mysql/database.hxx>
#include <iostream>
#include <fstream>
#include <map>
#include <string>
#include <strings.h>
//#include <stdlib.h> // exit()

#include "sanctiond.hpp"
#include "object.hpp"
#include "common.hpp"
#include "Socket.hpp"
#include "client.hpp"
#include "log.hpp"
#include "threadmgr.hpp"
#include "ansi.hpp"
#include "db.hpp"
#include "world.hpp"
#include "character.hpp"
#include "help.hpp"
#include "state.hpp"
#include "cmd.hpp"	/* cmd processor */

#define VERSION     "0.1"
#define MAX_FD      256
#define DEFAULT_MAX_THREADS 8
#define MAX_PASSWD_TRIES 5
#define DEFAULT_GAME_DB     "gsdata"
#define DEFAULT_CFG_DB      "gsconf"

static void	Version(void);
static void	Usage(void);
static void *  Login(void*);
static int	LoadHelpCommands();

extern void NewCharacter(Client *);

//std::map<int, Client *>  clients;
std::map<std::string, std::string> cfgSettings;
// An alternative to the above (having all the help data in RAM) is to index
// the helpfile and just store the indices and index into the file when
// needed.  That is what the next class/object is about.
HelpFileIndex helpIndex;
//std::map<std::string,size_t>	stateMap;
// Memento pattern for state
State	stateMap;
std::list<uint32_t>	bannedIPList;
//Cmd	cmdProcessor;


fd_set  readSet, writeSet;

World       theWorld;
std::string dbhost, dbschema, dbuser, dbpass, cfgdb, cfgpass, cfguser;
ServerCfg * conf = new ServerCfg();


int	main(int argc, char ** argv)
{
    int c, p;
    bool bVerbose = false;
	std::string s;
    extern char * optarg;
    extern int optind, optopt;
    boost::asio::io_service io_svc;
    boost::asio::ip::tcp::endpoint	listen_ep(boost::asio::ip::tcp::v4(),DEFAULT_MUD_PORT);
    boost::asio::ip::tcp::endpoint	listen_test_ep(boost::asio::ip::tcp::v4(),DEFAULT_TEST_PORT);
    boost::asio::ip::tcp::endpoint	bcast_ep(boost::asio::ip::tcp::v4(), DEFAULT_BCAST_PORT);


    // Startup the logger asap so we can catch DEBUG and ERROR msgs
    debug::log = new Log("server.log");
    debug::log->Level(LOG_DEBUG);  // catch everything until set on commandline

    // This DB reads an [ini] file for MySQL DB parameters.  This is a better
    // way than passing username/password on the commandline or compiling it
    // into the server.
//    ServerCfg * conf = new ServerCfg();
    if (!conf->Init()) {
        std::cerr << "Could not read server.conf" << std::endl;
        exit(-2);
    }
    p = conf->Parse();
    debug::log->out(LOG_DEBUG,"Added %i A/V pairs from server.conf",p);
    std::cout << "Added " << p << " A/V pairs from server.conf" << std::endl;

    dbhost = conf->GetValue("dbhost");
    dbschema = conf->GetValue("dbname");
    dbuser = conf->GetValue("dbuser");
    dbpass = conf->GetValue("dbpass");

    cfgdb = conf->GetValue("dbconfig");
    cfguser = conf->GetValue("dbcfguser");
    cfgpass = conf->GetValue("dbcfgpass");

//    delete conf;

    ////////////////////////////////[ Command-line Processing Section
    //
    while((c = getopt(argc,argv,"dhVvs:f:u:p:c:U:P:")) != -1) {
        switch(c) {
        case 'c':
            cfgdb = (char*)optarg;
            break;
        case 'U':
            cfguser = (char*)optarg;
            break;
        case 'P':
            cfgpass = (char*)optarg;
            break;
        case 's':
            dbhost = (char*)optarg;
            break;
        case'f':
            dbschema = (char*)optarg;
            break;
        case 'u':
            dbuser = (char*)optarg;
            break;
        case 'p':
            dbpass = (char*)optarg;
            break;
        case 'h':
            Usage();
            _exit(0);
            break;
        case 'V':
            bVerbose = true;
            break;
        case 'd':
            debug::log->Level(LOG_DEBUG);
            break;
        case 'v':
            Version();
            exit(0);
        case ':':
            std::cout << "That option requires an argument." << std::endl;
            exit(1);
        case '?':
            std::cerr << "Unrecognized option: " << (char)optopt << std::endl;
            exit(1);
        }   

    }   

    ////////////////////////////[ DataBase Initialization Section
    // Initialize the MySQL DB
    std::auto_ptr<odb::core::database> db (new odb::mysql::database(argc,argv));
    odb::transaction t(db->begin());

    //////////////////[ Load Areas, objects, items, NPC's, etc.
    // The world should at least have a help tutorial section and Agency HQ
    // to start.  These are loaded and always paged in.  Other areas are
    // read from the DB as they are visited.  Maybe here would be a good place
    // to have a paging system, keeping "hot zones" with high activity in
    // memory and paging them out when they get cold.
//    Setting * helpSetting = (Setting*)objFactory.CreateObject(OType::Setting,"help_setting");
//    Setting * agencySetting = (Setting*)objFactory.CreateObject(OType::Setting,"agency_setting");

//    theWorld.AddSetting(helpSetting);
//    theWorld.AddSetting(agencySetting);

    Version();

    // Compile Help Index from HelpFile
    int numHelpCmds = LoadHelpCommands();
    std::cout << "Indexed " << numHelpCmds << " help topics." << std::endl;
    debug::log->out(LOG_DEBUG,"Indexed %d help topics.",numHelpCmds);

    ////////////[ Build cmd tree for cmd processor
    if (!cmd::init()) {
    	debug::log->out(LOG_FATAL,"Error initializing command processor");
    	exit(1);
    }

    //////////////////////////////////////[ Socket/Network Section
    // IPv4
    mud_network	server(io_svc, listen_ep, bcast_ep);

    debug::log->out(LOG_INFO, "Bound socket on port %d",DEFAULT_MUD_PORT);
    std::cout << "Bound socket on port " << DEFAULT_MUD_PORT << std::endl;

    ////////////////////////////////[ Main Process Loop
    // TODO Must check IP against baniplist or other ACL
    io_svc.run();


}

void  * Login(void * c)
{
    debug::log->out(LOG_DEBUG,"%s",__PRETTY_FUNCTION__);

    Client * cli = reinterpret_cast<Client*>(c);
    char    buff[256];
    //wchar_t wbuff[256];
    int s = cli->Sock();

    // Display MOTD
    std::ifstream	motdfs(MOTD);
    if (motdfs.good()) {
    	while(!motdfs.eof()) {
    		motdfs.getline(buff,256);
    		Socket::WriteLine(s,buff);
    	}
    }
    motdfs.close();

//    Socket::WriteLine(s,ANSI_640x480_CO);
//    FONT_BLINKS(s);
    FONT_BRED(s);
    Socket::WriteLine(s, "*** WARNING *** WARNING *** WARNING *** WARNING *** WARNING ***");
    FONT_BOLD(s); FONT_WHITE(s);
    Socket::Write(s, ".................oO["); FONT_YELLOW(s); //FONT_OVER(s);
    Socket::Write(s,"Executive Defense Directorate"); FONT_WHITE(s);
    Socket::WriteLine(s,"]Oo..................\r\n");
    FONT_NORMAL(s); //FONT_GREEN(s);
    Socket::WriteLine(s, "*** You have connected to the Government Sanctioned Network Terminal.");
    Socket::WriteLine(s, "*** Authorized Personnel Only");
    Socket::WriteLine(s, "*** Login with your agent ID or type new if you are a new agent.");
    Socket::Write(s, "Login: ");
    int bytes = Socket::ReadLine(s,&buff[0],256);

    buff[bytes-2] = '\0';   // get rid of the '\r\n' at the end
    debug::log->out(LOG_DEBUG,"debug::login(%d/%d): \"%s\"",bytes,strlen(buff),buff);
    // TODO check username and password
    if (!strcmp(buff,"new")) {       
        NewCharacter(cli);

    } else {
        bool psucc = false;
        char u[MAX_USERNAME_LEN];
        char p[MAX_PASSWD_LEN];
        strcpy(u,buff);
        for (int i = 0; i < MAX_PASSWD_TRIES && !psucc; i++) {
            //mbstowcs(&wbuff[0],&buff[0],256);
            //cli->SetName(wbuff);

            Socket::Write(s,"Password: ");
            bytes = Socket::ReadLine(s,&p[0],MAX_PASSWD_LEN);
            buff[bytes-2] = '\0';

            // TODO check password against passwd in client DB
            sprintf(buff,"SELECT * FROM 'gsdata'.'account' WHERE 'account'.'name' == %s",u);
            DBRef	passwdCheck(buff,pass);
            mysqlpp::StoreQueryResult passwdCheck.ExecuteQuery();

            sprintf(buff,"Login[\033[31m%d\033[0m]: ",i+1);
            Socket::Write(s, buff);
            bytes = Socket::ReadLine(s,&u[0],MAX_USERNAME_LEN);

            buff[bytes-2] = '\0';   // get rid of the '\r\n' at the end
        }
        if (!psucc) {
        Socket::WriteLine(s,"If you have forgotten your username or password, visit the");
        Socket::WriteLine(s,"http://www.government-sanctioned.us/ website and reset it.");
        Socket::WriteLine(s,"Goodbye!");
        close(s);
        delete cli;
        return NULL;
        }
    }

    // Add to fd_set and client map
    pthread_mutex_lock(&ThreadMgr::ReadSetMutex());
    FD_SET(cli->Sock(),&readSet);
    pthread_mutex_unlock(&ThreadMgr::ReadSetMutex());
    pthread_mutex_lock(&ThreadMgr::WriteSetMutex());
    FD_SET(cli->Sock(),&writeSet);
    pthread_mutex_unlock(&ThreadMgr::WriteSetMutex());

    // TODO Key maybe should be changed to cli->Sock() since it'll be the
    // socket that we pull from our read/write set threads.
    //clients[cli->Sock()] = cli;
    clients.insert(cli);

    return cli;
}

//! Load command help from text file into memory-based container
int LoadHelpCommands()
{
	return helpIndex.Compile();
}

void Version()
{
    std::cout << std::endl;
    std::cout << "Government Sanctioned RPG Server v" << VERSION << std::endl;
	std::cout << "Build: " << BUILD_NUM << " Build Date: " << BUILD_DATE << std::endl;
    std::cout << "Copyright 2010 B. Gian James" << std::endl;
    std::cout << "Distributed under BSD open source license. Share and Enjoy!" << std::endl;
    std::cout << "http://www.government-sanctioned.us/" << std::endl;
    std::cout << std::endl;
}

void Usage()
{
    using namespace std;
    cout << endl;
    cout << "Usage: sanctiond [-vVh] [-s <ip>] [-p <port>]" << endl;
    cout << "Where:" << endl;
    cout << "       -v          Prints version." << endl;
    cout << "       -V          Turns on verbosity." << endl;
    cout << "       -d          Turns on debugging." << endl;
    cout << "       -h          Shows this help." << endl;
    cout << "       -s <ip>     Bind to <ip>" << endl;
    cout << "       -u <user>   Username" << endl;
    cout << "       -p <pass>   User password" << endl;
    cout << "       -U <user>   Cfgdb username" << endl;
    cout << "       -P <pass>   Cfgdb password" << endl;
    cout << "       -f <schema> DB schema" << endl;
    cout << "       -c <db>     Cfg DB" << endl;
    cout << endl;
}


World &     GlobalGetWorld() { return theWorld; }
ObjFactory &GlobalGetFactory() { return objFactory; }
MySQLDB &   GlobalGetGameDB() { return *gsdata; }
MySQLDB &   GlobalGetAreaDB() { return *gsarea; }
std::map<std::string,std::string> & GlobalGetCfgSettings() {
    return cfgSettings;
}
Log &       GlobalGetLogger() { return *debug::log; }
//std::map<std::string,HelpFileValue> & GlobalGetHelpCommands() { return helpMap; }
clientSet &	GlobalGetClients() { return clients; }
State & GlobalGetState() { return stateMap; }

